/*
 * ARQQueue.cpp
 *
 *  Created on: 27.11.2012
 *      Author: mario
 */

#include "ARQQueue.h"

#include "UI_multiplexer.h"

#include <stdlib.h>
#include <time.h>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <fstream>

#include "ariba/utility/messages.h"

using boost::property_tree::ptree;


ARQ_Queue::ARQ_Queue()
{
    /* initialize random seed: */
    srand ( time(NULL) );
}

ARQ_Queue::~ARQ_Queue()
{
}

omnibus_seq_num ARQ_Queue::get_next_seqnum(UserPtr user)
{
    ARQ_Queue::MessageList& list = arq_map[user->get_name()];

    omnibus_seq_num seq;
    
    // BRANCH: message list is empty --> create random seq_num
    if ( list.empty() )
    {
        // create random seq_num
        seq = rand();
        seq = seq << 32;  // XXX not nice.. 64bit random generator would be better, and the bit number should not be hard-coded..
        seq += rand();
    }
    
    // BRANCH: message list is not empty --> return next seq_num
    else
    {
        seq = list.back().seq_num + 1;
    }
    
    return seq;
}



void ARQ_Queue::push_message(UserPtr user, omnibus_seq_num seqnum, TMsgPtr msg)
{
    // filename
    ostringstream out;
    out << user->get_masked_name() << "__" << seqnum << ".msg";
    string filename = out.str();
    
    
    // insert into arq_queue
    ARQ_Queue::MessageList& list = arq_map[user->get_name()];
    list.push_back(ARQ_Queue_Entry(seqnum, filename, "Receipt not implemented, yet.", msg));
}



void ARQ_Queue::remove_message(UserPtr user, omnibus_seq_num seqnum)
{
    ARQ_Queue::MessageList& list = arq_map[user->get_name()];
    
    ARQ_Queue::MessageList::iterator it;
    for ( it=list.begin() ; it != list.end(); it++ )
    {
        if ( it->seq_num == seqnum )
        {
            list.erase(it);
            
            return;
        }
    }
}

void ARQ_Queue::get_waiting_messages(vector<TMsgPtr>& messages, UserPtr user)
{
    ARQ_Queue::PerUserMap::iterator it;
    it = arq_map.find(user->get_name());

    // no messages waiting --> return empty vector
    if ( it == arq_map.end() )
        return;
    
    
    /* pack all waiting messages into vector */
    ARQ_Queue::MessageList& list = it->second;
    messages.reserve(list.size());
    
    ARQ_Queue::MessageList::iterator list_it;
    for ( list_it=list.begin() ; list_it != list.end(); list_it++ )
    {
        messages.push_back(list_it->msg);
    }
}

// only for debugging
void ARQ_Queue::debug_show_queue(UserPtr user)
{
    ARQ_Queue::PerUserMap::iterator it;

    it = arq_map.find(user->get_name());

    // no messages waiting --> quit
    if ( it == arq_map.end() )
        return;

    // show all waiting messages
    ARQ_Queue::MessageList& list = it->second;
    
    ARQ_Queue::MessageList::iterator list_it;
    ostringstream out;
    out << "||| Messages waiting: ";
    for ( list_it=list.begin() ; list_it != list.end(); list_it++ )
    {
        out << list_it->seq_num << " ";
    }
    
    UI_multiplexer::instance().write_debug(out.str());
}


    
void ARQ_Queue::save_to_disk(const path& message_dir)
{
    // create message-dir if necessary
    if ( ! boost::filesystem::exists(message_dir) )
    {
        bool ok = boost::filesystem::create_directories(message_dir);
        
        // TODO if ( ! ok ) throw ...
    }

    
    /* save arq list */
    ptree root_pt;
    ptree& users = root_pt.put_child("arq_queue", ptree());
    
    for ( PerUserMap::iterator map_it=arq_map.begin() ; map_it != arq_map.end(); map_it++ )
    {
        // messages-list
        MessageList& list = map_it->second;
        
        // ignore users with no waiting messages
        if ( list.empty() )
            continue;
        
        
        ptree& user = users.add_child("user", ptree());
        
        // user name
        user.put("name", map_it->first);
        ptree& messages = user.put_child("messages", ptree());
        
        // all messages to this user
        for ( MessageList::iterator list_it=list.begin() ; list_it != list.end(); list_it++ )
        {
            /* store meta data */
            ptree& message = messages.add_child("message", ptree());
            
            message.put("seq_num", list_it->seq_num);
            message.put("filename", list_it->filename);
            message.put("receipt", list_it->receipt);
            
            
            /* store message (binary) */
            // serialize
            ariba::Message msg;
            msg.encapsulate(list_it->msg.get());
            
            // write
            path filename = message_dir / list_it->filename; 
            ofstream msg_file(filename.string().c_str(), ofstream::binary);
            msg_file.write((char*) msg.getPayload().getBuffer(), msg.getPayload().getLength() / 8);
            msg_file.close();
                        
            
//            // XXX debug
//            read_message(filename);
        }
    }


    /* write XML */
    // pretty xml output settings
    boost::property_tree::xml_writer_settings<char> w(' ', 4);
    
    // * write *
    path xml_file = message_dir / "messages.xml"; 
    boost::property_tree::write_xml(xml_file.string(), root_pt, std::locale(), w);
}



void ARQ_Queue::load_from_disk(const path& message_dir)
{
    // read xml file
    path xml_file = message_dir / "messages.xml";
    
    if ( ! boost::filesystem::exists(xml_file) )
    {
        // nothing to do.
        return;
    }
        
    
    boost::property_tree::ptree pt;
    boost::property_tree::read_xml(xml_file.string(), pt);

    // load messages from xml file
    BOOST_FOREACH(ptree::value_type& user_it, pt.get_child("arq_queue"))
    {
        /* get user */
        ptree& user_pt = user_it.second;
        const string& name = user_pt.get<string>("name");
        UI.write_debug(name);
        
        
        /* load messages */
        BOOST_FOREACH(ptree::value_type& message_it, user_pt.get_child("messages"))
        {
            ptree& message_pt = message_it.second;
            
            // get data from ptree
            omnibus_seq_num seqnum = message_pt.get<omnibus_seq_num>("seq_num");
            string filename = message_pt.get<string>("filename");
            string receipt = message_pt.get<string>("receipt");
            path msg_path = message_dir / filename;
            
            // load message (binary data)
            if ( boost::filesystem::exists(msg_path) )
            {
                TMsgPtr msg = read_message(msg_path);
                
                // insert into arq_queue
                ARQ_Queue::MessageList& list = arq_map[name];
                list.push_back(ARQ_Queue_Entry(seqnum, filename, receipt, msg));
                UI.write_debug("Loading message: " + filename);
            }
            else
            {
                UI.write_warn("Message file '" + filename + "' is missing. Dropping message.");
            }
        }
    }
    
    
    // delete all message files (also files not listed in messages.xml)
    directory_iterator end;
    for ( directory_iterator it(message_dir); it != end; ++it )
    {
        if ( it->path().extension() == ".msg" )
        {
            boost::system::error_code ec;
            boost::filesystem::remove(*it, ec);
        }
    }
}

TMsgPtr ARQ_Queue::read_message(path filename)
{
    // open file
    ifstream infile(filename.string().c_str(),ifstream::binary);
    
    // get size of file
    long size;
    infile.seekg(0,ifstream::end);
    size=infile.tellg();
    infile.seekg(0);
    

    // read content of infile    (a bit complicated.. maybe we can do better in the future.)
    char* buffer = new char[size];
    infile.read (buffer,size);
    
    // create ariba message out of this data
    Data data((uint8_t*) buffer, size * 8);            
    ariba::Message* legacy_message = new ariba::Message(data);

    TransportMessage* msg = legacy_message->decapsulate<TransportMessage>();
    TMsgPtr tmsg(msg);
    
//    // XXX debug
//    ostringstream out;
//    out << "||| MARIO oO ||| LOADING: " << tmsg->getType() << ", " << tmsg->getSubtype() << ", " << tmsg->get_seq_num();
//    
//    // XXX DEBUG
//    PushRequestMessage* m = msg->decapsulate<PushRequestMessage>();
//    out << " --- " << m->get_key() << ", " << m->get_file_name();
//            
//    UI.write_debug(out.str());

    legacy_message->setReleasePayload(false);
    delete legacy_message;
    
    return tmsg;
}
